/*
 * @(#)HtmlDocletWriter.java 1.178 04/05/25
 * 
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. SUN
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package net.sf.odinms.exttools.doclet.writers;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import com.sun.javadoc.*;
import com.sun.tools.doclets.formats.html.markup.HtmlDocWriter;
import com.sun.tools.doclets.internal.toolkit.AnnotationTypeWriter;
import com.sun.tools.doclets.internal.toolkit.ClassWriter;
import com.sun.tools.doclets.internal.toolkit.Configuration;
import com.sun.tools.doclets.internal.toolkit.PackageSummaryWriter;
import com.sun.tools.doclets.internal.toolkit.taglets.DocRootTaglet;
import com.sun.tools.doclets.internal.toolkit.taglets.TagletOutput;
import com.sun.tools.doclets.internal.toolkit.taglets.TagletWriter;
import com.sun.tools.doclets.internal.toolkit.util.DirectoryManager;
import com.sun.tools.doclets.internal.toolkit.util.DocletConstants;
import com.sun.tools.doclets.internal.toolkit.util.SourceToHTMLConverter;
import com.sun.tools.doclets.internal.toolkit.util.Util;
import net.sf.odinms.exttools.doclet.Config;

/**
 * Class for the Html Format Code Generation specific to JavaDoc. This Class
 * contains methods related to the Html Code Generation which are used
 * extensively while generating the entire documentation.
 * 
 * @since JDK1.2
 * @author Atul M Dambalkar
 * @author Robert Field
 * 
 * @deprecated Legacy code from Standard Doclet.
 */
public class HtmlDocletWriter extends HtmlDocWriter {
	
	/**
	 * Relative path from the file getting generated to the destination
	 * directory. For example, if the file getting generated is
	 * "java/lang/Object.html", then the relative path string is "../../". This
	 * string can be empty if the file getting generated is in the destination
	 * directory.
	 */
	public String relativePath = "";
	
	/**
	 * Same as relativepath, but normalized to never be empty or end with a
	 * slash.
	 */
	public String relativepathNoSlash = "";
	
	/**
	 * Platform-dependent directory path from the current or the destination
	 * directory to the file getting generated. Used when creating the file. For
	 * example, if the file getting generated is "java/lang/Object.html", then
	 * the path string is "java/lang".
	 */
	public String path = "";
	
	/**
	 * Name of the file getting generated. If the file getting generated is
	 * "java/lang/Object.html", then the filename is "Object.html".
	 */
	public String filename = "";
	
	/**
	 * The display length used for indentation while generating the class page.
	 */
	public int displayLength = 0;
	
	/**
	 * The global configuration information for this run.
	 */
	public Config configuration;
	
	/**
	 * Constructor to construct the HtmlStandardWriter object.
	 * 
	 * @param filename File to be generated.
	 */
	public HtmlDocletWriter(Config configuration, String filename)
			throws IOException {
		super(configuration, filename);
		this.configuration = configuration;
		this.filename = filename;
	}
	
	/**
	 * Constructor to construct the HtmlStandardWriter object.
	 * 
	 * @param path Platform-dependent {@link #path} used when creating file.
	 * @param filename Name of file to be generated.
	 * @param relativePath Value for the variable {@link #relativePath}.
	 */
	public HtmlDocletWriter(Config configuration, String path,
			String filename, String relativePath) throws IOException {
		super(configuration, path, filename);
		this.configuration = configuration;
		this.path = path;
		this.relativePath = relativePath;
		this.relativepathNoSlash = DirectoryManager.getPathNoTrailingSlash(this.relativePath);
		this.filename = filename;
	}
	
	/**
	 * Replace {&#064;docRoot} tag used in options that accept HTML text, such
	 * as -header, -footer and -bottom, and when converting a relative HREF
	 * where commentTagsToString inserts a {&#064;docRoot} where one was
	 * missing. (Also see DocRootTaglet for {&#064;docRoot} tags in doc
	 * comments.)
	 * <p>
	 * Replace {&#064;docRoot} tag in htmlstr with the relative path to the
	 * destination directory from the directory where the file is being written,
	 * looping to handle all such tags in htmlstr.
	 * <p>
	 * For example, for "-d docs" and -header containing {&#064;docRoot}, when
	 * the HTML page for source file p/C1.java is being generated, the
	 * {&#064;docRoot} tag would be inserted into the header as "../", the
	 * relative path from docs/p/ to docs/ (the document root).
	 * <p>
	 * Note: This doc comment was written with '&amp;#064;' representing '@' to
	 * prevent the inline tag from being interpreted.
	 */
	public String replaceDocRootDir(String htmlstr) {
		// Return if no inline tags exist
		int index = htmlstr.indexOf("{@");
		if (index < 0) {
			return htmlstr;
		}
		String lowerHtml = htmlstr.toLowerCase();
		// Return index of first occurrence of {@docroot}
		// Note: {@docRoot} is not case sensitive when passed in w/command line
		// option
		index = lowerHtml.indexOf("{@docroot}", index);
		if (index < 0) {
			return htmlstr;
		}
		StringBuffer buf = new StringBuffer();
		int previndex = 0;
		while (true) {
			// Search for lowercase version of {@docRoot}
			index = lowerHtml.indexOf("{@docroot}", previndex);
			// If next {@docRoot} tag not found, append rest of htmlstr and exit
			// loop
			if (index < 0) {
				buf.append(htmlstr.substring(previndex));
				break;
			}
			// If next {@docroot} tag found, append htmlstr up to start of tag
			buf.append(htmlstr.substring(previndex, index));
			previndex = index + 10; // length for {@docroot} string
			// Insert relative path where {@docRoot} was located
			buf.append(relativepathNoSlash);
			// Append slash if next character is not a slash
			if (relativepathNoSlash.length() > 0
					&& previndex < htmlstr.length()
					&& htmlstr.charAt(previndex) != '/') {
				buf.append(DirectoryManager.URL_FILE_SEPERATOR);
			}
		}
		return buf.toString();
	}
	
	/**
	 * Print Html Hyper Link, with target frame. This link will only appear if
	 * page is not in a frame.
	 * 
	 * @param link String name of the file.
	 * @param where Position in the file
	 * @param target Name of the target frame.
	 * @param label Tag for the link.
	 * @param bold Whether the label should be bold or not?
	 */
	public void printNoFramesTargetHyperLink(String link, String where,
			String target, String label, boolean bold) {
		// println(Tags.open("script"));
		// println(" <!--");
		// println(" if(window==top) {");
		// println(" document.writeln('"
		// + getHyperLink(link, where, label, bold, "", "", target) + "');");
		// println(" }");
		// println(" //-->");
		// println(Tags.close("script"));
		// println(Tags.open("noscript"));
		// println(" " + getHyperLink(link, where, label, bold, "", "",
		// target));
		// println(Tags.close("noscript"));
		// println(DocletConstants.NL);
	}
	
	private void printMethodInfo(MethodDoc method) {
		ClassDoc[] intfacs = method.containingClass().interfaces();
		MethodDoc overriddenMethod = method.overriddenMethod();
		if (intfacs.length > 0 || overriddenMethod != null) {
			// println(Tags.open("dd"));
			printTagsInfoHeader();
			MethodWriterImpl.printImplementsInfo(this, method);
			if (overriddenMethod != null) {
				MethodWriterImpl.printOverridden(this, method.overriddenType(),
						overriddenMethod);
			}
			printTagsInfoFooter();
			// println(Tags.close("dd"));
		}
		dd();
	}
	
	protected void printTags(Doc doc) {
		if (configuration.nocomment) {
			return;
		}
		if (doc instanceof MethodDoc) {
			printMethodInfo((MethodDoc) doc);
		}
		TagletOutputImpl output = new TagletOutputImpl("");
		TagletWriter.genTagOuput(configuration.tagletManager, doc,
				configuration.tagletManager.getCustomTags(doc),
				getTagletWriterInstance(false), output);
		if (output.toString().trim().length() > 0) {
			printTagsInfoHeader();
			print(output.toString());
			printTagsInfoFooter();
		} else if (!(doc instanceof ConstructorDoc || doc instanceof RootDoc || doc instanceof ClassDoc)) {
			// To be consistent with 1.4.2 output.
			// I hate to do this but we have to pass the diff test to prove
			// nothing has broken.
			printTagsInfoHeader();
			printTagsInfoFooter();
		}
	}
	
	/**
	 * Returns a TagletWriter that knows how to write HTML.
	 * 
	 * @return a TagletWriter that knows how to write HTML.
	 */
	public TagletWriter getTagletWriterInstance(boolean isFirstSentence) {
		return new TagletWriterImpl(this, isFirstSentence);
	}
	
	protected void printTagsInfoHeader() {
		// println(Tags.open("dl"));
	}
	
	protected void printTagsInfoFooter() {
		// println(Tags.close("dl"));
	}
	
	/**
	 * Print Package link, with target frame.
	 * 
	 * @param pd The link will be to the "package-summary.html" page for this
	 *        package.
	 * @param target Name of the target frame.
	 * @param label Tag for the link.
	 */
	public void printTargetPackageLink(PackageDoc pd, String target,
			String label) {
		print(getHyperLink(pathString(pd, "package-summary.html"), "", label,
				false, "", "", target));
	}
	
	/**
	 * Print the html file header. Also print Html page title and stylesheet
	 * default properties.
	 * 
	 * @param title String window title to go in the &lt;title&gt; tag
	 * @param metakeywords Array of String keywords for meta tag. Each element
	 *        of the array is assigned to a separate meta tag. Pass in null for
	 *        no array.
	 * @param includeScript boolean true if printing windowtitle script. False
	 *        for files that appear in the left-hand frames.
	 */
	public void printHtmlHeader(String title, String[] metakeywords,
			boolean includeScript) {
		// println(Tags.DTD_XHTML1_TRANS);
		// println(Tags.HTML_XML);
		// println(Tags.open("head"));
		//		
		// if (! configuration.notimestamp) {
		// print("<!-- Generated by javadoc (build "
		// +ConfigurationImpl.BUILD_DATE
		// +") on "+today()+" -->");
		// }
		// if (configuration.charset.length() > 0) {
		// println(Tags.empty("meta http-equiv=\"Content-Type\"
		// content=\"text/html\" "
		// +"charset="+configuration.charset));
		// }
		// if (configuration.windowtitle.length() > 0) {
		// title += " (" + configuration.windowtitle + ")";
		// }
		// winTitle = title;
		// println(Tags.close("title")+title+Tags.close("title"));
		//
		// println("");
		// if (metakeywords != null) {
		// for (int i=0; i < metakeywords.length; i++) {
		// println(Tags.empty("meta name=\"keywords\"
		// content=\""+metakeywords[i]+"\""));
		// }
		// }
		// println("");
		// printStyleSheetProperties();
		// println("");
		// // Don't print windowtitle script for overview-frame,
		// allclasses-frame
		// // and package-frame
		// if (includeScript) {
		// printWinTitleScript(title);
		// }
		// println("\n"+Tags.close("head")+"\n");
		body("white", includeScript);
	}
	
	/**
	 * Print user specified header and the footer.
	 * 
	 * @param header if true print the user provided header else print the user
	 *        provided footer.
	 */
	public void printUserHeaderFooter(boolean header) {
		if (header) {
			print(replaceDocRootDir(configuration.header));
		} else {
			if (configuration.footer.length() != 0) {
				print(replaceDocRootDir(configuration.footer));
			} else {
				print(replaceDocRootDir(configuration.header));
			}
		}
	}
	
	/**
	 * Print the user specified bottom.
	 */
	public void printBottom() {
		// println(Tags.empty("hr"));
		print(replaceDocRootDir(configuration.bottom));
	}
	
	/**
	 * Print the navigation bar for the Html page at the top and and the bottom.
	 * 
	 * @param header If true print navigation bar at the top of the page else
	 *        print the nevigation bar at the bottom.
	 */
	protected void navLinks(boolean header) {
		// All this has been taken care of in XhtmlPageWriter
	}
	
	/**
	 * Print the word "NEXT" to indicate that no link is available. Override
	 * this method to customize next link.
	 */
	protected void navLinkNext() {
		// navLinkNext(null);
	}
	
	/**
	 * Print the word "PREV" to indicate that no link is available. Override
	 * this method to customize prev link.
	 */
	protected void navLinkPrevious() {
		// navLinkPrevious(null);
	}
	
	/**
	 * Do nothing. This is the default method.
	 */
	protected void printSummaryDetailLinks() {
	}
	
	/**
	 * Print link to the "overview-summary.html" page.
	 */
	protected void navLinkContents() {
		navCellStart();
		printHyperLink(relativePath + "overview-summary.html", "",
				configuration.getText("doclet.Overview"), true, "NavBarFont1");
		navCellEnd();
	}
	
	/**
	 * Description for a cell in the navigation bar.
	 */
	protected void navCellStart() {
		print("  ");
		tdBgcolorStyle("#EEEEFF", "NavBarCell1");
		print("    ");
	}
	
	/**
	 * Description for a cell in the navigation bar, but with reverse high-light
	 * effect.
	 */
	protected void navCellRevStart() {
		print("  ");
		tdBgcolorStyle("#FFFFFF", "NavBarCell1Rev");
		print(" ");
		space();
	}
	
	/**
	 * Closing tag for navigation bar cell.
	 */
	protected void navCellEnd() {
		space();
		tdEnd();
	}
	
	/**
	 * Print "Tree" link in the navigation bar. If there is only one package
	 * specified on the command line, then the "Tree" link will be to the only
	 * "package-tree.html" file otherwise it will be to the "overview-tree.html"
	 * file.
	 */
	protected void navLinkTree() {
		navCellStart();
		PackageDoc[] packages = configuration.root.specifiedPackages();
		if (packages.length == 1
				&& configuration.root.specifiedClasses().length == 0) {
			printHyperLink(pathString(packages[0], "package-tree.html"), "",
					configuration.getText("doclet.Tree"), true, "NavBarFont1");
		} else {
			printHyperLink(relativePath + "overview-tree.html", "",
					configuration.getText("doclet.Tree"), true, "NavBarFont1");
		}
		navCellEnd();
	}
	
	/**
	 * Print help file link. If user has provided a help file, then generate a
	 * link to the user given file, which is already copied to current or
	 * destination directory.
	 */
	protected void navLinkHelp() {
		String helpfilenm = configuration.helpfile;
		if (helpfilenm.equals("")) {
			helpfilenm = "help-doc.html";
		} else {
			int lastsep;
			if ((lastsep = helpfilenm.lastIndexOf(File.separatorChar)) != -1) {
				helpfilenm = helpfilenm.substring(lastsep + 1);
			}
		}
		navCellStart();
		printHyperLink(relativePath + helpfilenm, "",
				configuration.getText("doclet.Help"), true, "NavBarFont1");
		navCellEnd();
	}
	
	/**
	 * Print the word "Detail" in the navigation bar. No link is available.
	 */
	protected void navDetail() {
		printText("doclet.Detail");
	}
	
	/**
	 * Print the word "Summary" in the navigation bar. No link is available.
	 */
	protected void navSummary() {
		printText("doclet.Summary");
	}
	
	/**
	 * Print the Html table tag for the index summary tables. The table tag
	 * printed is &lt;TABLE BORDER="1" CELLPADDING="3" CELLSPACING="0"
	 * WIDTH="100%">
	 */
	public void tableIndexSummary() {
		table(1, "100%", 3, 0);
	}
	
	/**
	 * Same as {@link #tableIndexSummary()}.
	 */
	public void tableIndexDetail() {
		table(1, "100%", 3, 0);
	}
	
	/**
	 * Print Html tag for table elements. The tag printed is &lt;TD
	 * ALIGN="right" VALIGN="top" WIDTH="1%"&gt;.
	 */
	public void tdIndex() {
		print("<TD ALIGN=\"right\" VALIGN=\"top\" WIDTH=\"1%\">");
	}
	
	/**
	 * Prine table header information about color, column span and the font.
	 * 
	 * @param color Background color.
	 * @param span Column span.
	 */
	public void tableHeaderStart(String color, int span) {
		trBgcolorStyle(color, "TableHeadingColor");
		thAlignColspan("left", span);
		font("+2");
	}
	
	/**
	 * Print table header for the inherited members summary tables. Print the
	 * background color information.
	 * 
	 * @param color Background color.
	 */
	public void tableInheritedHeaderStart(String color) {
		trBgcolorStyle(color, "TableSubHeadingColor");
		thAlign("left");
	}
	
	/**
	 * Print "Use" table header. Print the background color and the column span.
	 * 
	 * @param color Background color.
	 */
	public void tableUseInfoHeaderStart(String color) {
		trBgcolorStyle(color, "TableSubHeadingColor");
		thAlignColspan("left", 2);
	}
	
	/**
	 * Print table header with the background color with default column span 2.
	 * 
	 * @param color Background color.
	 */
	public void tableHeaderStart(String color) {
		tableHeaderStart(color, 2);
	}
	
	/**
	 * Print table header with the column span, with the default color #CCCCFF.
	 * 
	 * @param span Column span.
	 */
	public void tableHeaderStart(int span) {
		tableHeaderStart("#CCCCFF", span);
	}
	
	/**
	 * Print table header with default column span 2 and default color #CCCCFF.
	 */
	public void tableHeaderStart() {
		tableHeaderStart(2);
	}
	
	/**
	 * Print table header end tags for font, column and row.
	 */
	public void tableHeaderEnd() {
		fontEnd();
		thEnd();
		trEnd();
	}
	
	/**
	 * Print table header end tags in inherited tables for column and row.
	 */
	public void tableInheritedHeaderEnd() {
		thEnd();
		trEnd();
	}
	
	/**
	 * Print the summary table row cell attribute width.
	 * 
	 * @param width Width of the table cell.
	 */
	public void summaryRow(int width) {
		if (width != 0) {
			tdWidth(width + "%");
		} else {
			td();
		}
	}
	
	/**
	 * Print the summary table row cell end tag.
	 */
	public void summaryRowEnd() {
		tdEnd();
	}
	
	/**
	 * Print the heading in Html &lt;H2> format.
	 * 
	 * @param str The Header string.
	 */
	public void printIndexHeading(String str) {
		h2();
		print(str);
		h2End();
	}
	
	/**
	 * Print Html tag &lt;FRAMESET=arg&gt;.
	 * 
	 * @param arg Argument for the tag.
	 */
	public void frameSet(String arg) {
		println("<FRAMESET " + arg + ">");
	}
	
	/**
	 * Print Html closing tag &lt;/FRAMESET&gt;.
	 */
	public void frameSetEnd() {
		println("</FRAMESET>");
	}
	
	/**
	 * Print Html tag &lt;FRAME=arg&gt;.
	 * 
	 * @param arg Argument for the tag.
	 */
	public void frame(String arg) {
		println("<FRAME " + arg + ">");
	}
	
	/**
	 * Print Html closing tag &lt;/FRAME&gt;.
	 */
	public void frameEnd() {
		println("</FRAME>");
	}
	
	/**
	 * Return path to the class page for a classdoc. For example, the class name
	 * is "java.lang.Object" and if the current file getting generated is
	 * "java/io/File.html", then the path string to the class, returned is
	 * "../../java/lang.Object.html".
	 * 
	 * @param cd Class to which the path is requested.
	 */
	protected String pathToClass(ClassDoc cd) {
		return pathString(cd.containingPackage(), cd.name() + ".html");
	}
	
	/**
	 * Return the path to the class page for a classdoc. Works same as
	 * {@link #pathToClass(ClassDoc)}.
	 * 
	 * @param cd Class to which the path is requested.
	 * @param name Name of the file(doesn't include path).
	 */
	protected String pathString(ClassDoc cd, String name) {
		return pathString(cd.containingPackage(), name);
	}
	
	/**
	 * Return path to the given file name in the given package. So if the name
	 * passed is "Object.html" and the name of the package is "java.lang", and
	 * if the relative path is "../.." then returned string will be
	 * "../../java/lang/Object.html"
	 * 
	 * @param pd Package in which the file name is assumed to be.
	 * @param name File name, to which path string is.
	 */
	protected String pathString(PackageDoc pd, String name) {
		StringBuffer buf = new StringBuffer(relativePath);
		buf.append(DirectoryManager.getPathToPackage(pd, name));
		return buf.toString();
	}
	
	/**
	 * Print the link to the given package.
	 * 
	 * @param pkg the package to link to.
	 * @param label the label for the link.
	 * @param isBold true if the label should be bold.
	 */
	public void printPackageLink(PackageDoc pkg, String label, boolean isBold) {
		print(getPackageLink(pkg, label, isBold));
	}
	
	/**
	 * Print the link to the given package.
	 * 
	 * @param pkg the package to link to.
	 * @param label the label for the link.
	 * @param isBold true if the label should be bold.
	 * @param style the font of the package link label.
	 */
	public void printPackageLink(PackageDoc pkg, String label, boolean isBold,
			String style) {
		print(getPackageLink(pkg, label, isBold, style));
	}
	
	/**
	 * Return the link to the given package.
	 * 
	 * @param pkg the package to link to.
	 * @param label the label for the link.
	 * @param isBold true if the label should be bold.
	 * @return the link to the given package.
	 */
	public String getPackageLink(PackageDoc pkg, String label, boolean isBold) {
		return getPackageLink(pkg, label, isBold, "");
	}
	
	/**
	 * Return the link to the given package.
	 * 
	 * @param pkg the package to link to.
	 * @param label the label for the link.
	 * @param isBold true if the label should be bold.
	 * @param style the font of the package link label.
	 * @return the link to the given package.
	 */
	public String getPackageLink(PackageDoc pkg, String label, boolean isBold,
			String style) {
		boolean included = pkg != null && pkg.isIncluded();
		if (!included) {
			PackageDoc[] packages = configuration.packages;
			for (int i = 0; i < packages.length; i++) {
				if (packages[i].equals(pkg)) {
					included = true;
					break;
				}
			}
		}
		if (included || pkg == null) {
			return getHyperLink(pathString(pkg, "package-summary.html"), "",
					label, isBold, style);
		} else {
			String crossPkgLink = getCrossPackageLink(Util.getPackageName(pkg));
			if (crossPkgLink != null) {
				return getHyperLink(crossPkgLink, "", label, isBold, style);
			} else {
				return label;
			}
		}
	}
	
	public String italicsClassName(ClassDoc cd, boolean qual) {
		String name = (qual) ? cd.qualifiedName() : cd.name();
		return (cd.isInterface()) ? italicsText(name) : name;
	}
	
	public void printSrcLink(ProgramElementDoc d, String label) {
		if (d == null) {
			return;
		}
		ClassDoc cd = d.containingClass();
		if (cd == null) {
			// d must be a class doc since in has no containing class.
			cd = (ClassDoc) d;
		}
		String href = relativePath + DocletConstants.SOURCE_OUTPUT_DIR_NAME
				+ DirectoryManager.getDirectoryPath(cd.containingPackage())
				+ cd.name() + ".html#" + SourceToHTMLConverter.getAnchorName(d);
		printHyperLink(href, "", label, true);
	}
	
	/**
	 * Return the link to the given class.
	 * 
	 * @param linkInfo the information about the link.
	 * 
	 * @return the link for the given class.
	 */
	public String getLink(LinkInfoImpl linkInfo) {
		LinkFactoryImpl factory = new LinkFactoryImpl(this);
		String link = ((LinkOutputImpl) factory.getLinkOutput(linkInfo)).toString();
		displayLength += linkInfo.displayLength;
		return link;
	}
	
	/**
	 * Return the type parameters for the given class.
	 * 
	 * @param linkInfo the information about the link.
	 * @return the type for the given class.
	 */
	public String getTypeParameterLinks(LinkInfoImpl linkInfo) {
		LinkFactoryImpl factory = new LinkFactoryImpl(this);
		return ((LinkOutputImpl) factory.getTypeParameterLinks(linkInfo, false)).toString();
	}
	
	/**
	 * Print the link to the given class.
	 */
	public void printLink(LinkInfoImpl linkInfo) {
		print(getLink(linkInfo));
	}
	
	/***************************************************************************
	 * Return a class cross link to external class documentation. The name must
	 * be fully qualified to determine which package the class is in. The -link
	 * option does not allow users to link to external classes in the "default"
	 * package.
	 * 
	 * @param qualifiedClassName the qualified name of the external class.
	 * @param refMemName the name of the member being referenced. This should be
	 *        null or empty string if no member is being referenced.
	 * @param label the label for the external link.
	 * @param bold true if the link should be bold.
	 * @param style the style of the link.
	 * @param code true if the label should be code font.
	 */
	public String getCrossClassLink(String qualifiedClassName,
			String refMemName, String label, boolean bold, String style,
			boolean code) {
		String className = "", packageName = qualifiedClassName == null
				? ""
				: qualifiedClassName;
		int periodIndex;
		while ((periodIndex = packageName.lastIndexOf('.')) != -1) {
			className = packageName.substring(periodIndex + 1,
					packageName.length())
					+ (className.length() > 0 ? "." + className : "");
			String defaultLabel = code
					? getCode() + className + getCodeEnd()
					: className;
			packageName = packageName.substring(0, periodIndex);
			if (getCrossPackageLink(packageName) != null) {
				// The package exists in external documentation, so link to the
				// external
				// class (assuming that it exists). This is definitely a
				// limitation of
				// the -link option. There are ways to determine if an external
				// package
				// exists, but no way to determine if the external class exists.
				// We just
				// have to assume that it does.
				return getHyperLink(configuration.extern.getExternalLink(
						packageName, relativePath, className + ".html"),
						refMemName == null ? "" : refMemName, label == null
								|| label.length() == 0 ? defaultLabel : label,
						bold, style, configuration.getText(
								"doclet.Href_Title_Class_Or_Interface",
								packageName), "");
			}
		}
		return null;
	}
	
	public boolean isClassLinkable(ClassDoc cd) {
		if (cd.isIncluded()) {
			return configuration.isGeneratedDoc(cd);
		}
		return configuration.extern.isExternal(cd);
	}
	
	public String getCrossPackageLink(String pkgName) {
		return configuration.extern.getExternalLink(pkgName, relativePath,
				"package-summary.html");
	}
	
	public void printQualifiedClassLink(int context, ClassDoc cd) {
		printLink(new LinkInfoImpl(context, cd, configuration.getClassName(cd),
				""));
	}
	
	/**
	 * Print Class link, with only class name as the link and prefixing plain
	 * package name.
	 */
	public void printPreQualifiedClassLink(int context, ClassDoc cd) {
		print(getPreQualifiedClassLink(context, cd, false));
	}
	
	/**
	 * Retrieve the class link with the package portion of the label in plain
	 * text. If the qualifier is excluded, it willnot be included in the link
	 * label.
	 * 
	 * @param cd the class to link to.
	 * @param isBold true if the link should be bold.
	 * @return the link with the package portion of the label in plain text.
	 */
	public String getPreQualifiedClassLink(int context, ClassDoc cd,
			boolean isBold) {
		String classlink = "";
		PackageDoc pd = cd.containingPackage();
		if (pd != null && !configuration.shouldExcludeQualifier(pd.name())) {
			classlink = getPkgName(cd);
		}
		classlink += getLink(new LinkInfoImpl(context, cd, cd.name(), isBold));
		return classlink;
	}
	
	/**
	 * Print Class link, with only class name as the bold link and prefixing
	 * plain package name.
	 */
	public void printPreQualifiedBoldClassLink(int context, ClassDoc cd) {
		print(getPreQualifiedClassLink(context, cd, true));
	}
	
	public void printText(String key) {
		print(configuration.getText(key));
	}
	
	public void printText(String key, String a1) {
		print(configuration.getText(key, a1));
	}
	
	public void printText(String key, String a1, String a2) {
		print(configuration.getText(key, a1, a2));
	}
	
	public void boldText(String key) {
		bold(configuration.getText(key));
	}
	
	public void boldText(String key, String a1) {
		bold(configuration.getText(key, a1));
	}
	
	public void boldText(String key, String a1, String a2) {
		bold(configuration.getText(key, a1, a2));
	}
	
	/**
	 * Print the link for the given member.
	 * 
	 * @param context the id of the context where the link will be printed.
	 * @param doc the member being linked to.
	 * @param label the label for the link.
	 * @param bold true if the link should be bold.
	 */
	public void printDocLink(int context, MemberDoc doc, String label,
			boolean bold) {
		print(getDocLink(context, doc, label, bold));
	}
	
	/**
	 * Print the link for the given member.
	 * 
	 * @param context the id of the context where the link will be printed.
	 * @param classDoc the classDoc that we should link to. This is not
	 *        necessarily equal to doc.containingClass(). We may be inheriting
	 *        comments.
	 * @param doc the member being linked to.
	 * @param label the label for the link.
	 * @param bold true if the link should be bold.
	 */
	public void printDocLink(int context, ClassDoc classDoc, MemberDoc doc,
			String label, boolean bold) {
		print(getDocLink(context, classDoc, doc, label, bold));
	}
	
	/**
	 * Return the link for the given member.
	 * 
	 * @param context the id of the context where the link will be printed.
	 * @param doc the member being linked to.
	 * @param label the label for the link.
	 * @param bold true if the link should be bold.
	 * @return the link for the given member.
	 */
	public String getDocLink(int context, MemberDoc doc, String label,
			boolean bold) {
		return getDocLink(context, doc.containingClass(), doc, label, bold);
	}
	
	/**
	 * Return the link for the given member.
	 * 
	 * @param context the id of the context where the link will be printed.
	 * @param classDoc the classDoc that we should link to. This is not
	 *        necessarily equal to doc.containingClass(). We may be inheriting
	 *        comments.
	 * @param doc the member being linked to.
	 * @param label the label for the link.
	 * @param bold true if the link should be bold.
	 * @return the link for the given member.
	 */
	public String getDocLink(int context, ClassDoc classDoc, MemberDoc doc,
			String label, boolean bold) {
		if (!(doc.isIncluded() || Util.isLinkable(classDoc, configuration()))) {
			return label;
		} else if (doc instanceof ExecutableMemberDoc) {
			ExecutableMemberDoc emd = (ExecutableMemberDoc) doc;
			return getLink(new LinkInfoImpl(context, classDoc, getAnchor(emd),
					label, bold));
		} else if (doc instanceof MemberDoc) {
			return getLink(new LinkInfoImpl(context, classDoc, doc.name(),
					label, bold));
		} else {
			return label;
		}
	}
	
	public void anchor(ExecutableMemberDoc emd) {
		anchor(getAnchor(emd));
	}
	
	public String getAnchor(ExecutableMemberDoc emd) {
		StringBuilder signature = new StringBuilder(emd.signature());
		StringBuilder signatureParsed = new StringBuilder();
		int counter = 0;
		for (int i = 0; i < signature.length(); i++) {
			char c = signature.charAt(i);
			if (c == '<') {
				counter++;
			} else if (c == '>') {
				counter--;
			} else if (counter == 0) {
				signatureParsed.append(c);
			}
		}
		return emd.name() + signatureParsed.toString();
	}
	
	public String seeTagToString(SeeTag see) {
		String tagName = see.name();
		if (!(tagName.startsWith("@link") || tagName.equals("@see"))) {
			return "";
		}
		StringBuffer result = new StringBuffer();
		boolean isplaintext = tagName.toLowerCase().equals("@linkplain");
		String label = see.label();
		label = (label.length() > 0) ? ((isplaintext) ? label : getCode()
				+ label + getCodeEnd()) : "";
		String seetext = replaceDocRootDir(see.text());
		
		// Check if @see is an href or "string"
		if (seetext.startsWith("<") || seetext.startsWith("\"")) {
			result.append(seetext);
			return result.toString();
		}
		
		// The text from the @see tag. We will output this text when a label is
		// not specified.
		String text = (isplaintext) ? seetext : getCode() + seetext
				+ getCodeEnd();
		
		ClassDoc refClass = see.referencedClass();
		String refClassName = see.referencedClassName();
		MemberDoc refMem = see.referencedMember();
		String refMemName = see.referencedMemberName();
		if (refClass == null) {
			// @see is not referencing an included class
			PackageDoc refPackage = see.referencedPackage();
			if (refPackage != null && refPackage.isIncluded()) {
				// @see is referencing an included package
				String packageName = isplaintext
						? refPackage.name()
						: getCode() + refPackage.name() + getCodeEnd();
				result.append(getPackageLink(refPackage, label.length() == 0
						? packageName
						: label, false));
			} else {
				// @see is not referencing an included class or package. Check
				// for cross links.
				String classCrossLink, packageCrossLink = getCrossPackageLink(refClassName);
				if (packageCrossLink != null) {
					// Package cross link found
					result.append(getHyperLink(packageCrossLink, "",
							(label.length() == 0) ? text : label, false));
				} else if ((classCrossLink = getCrossClassLink(refClassName,
						refMemName, label, false, "", !isplaintext)) != null) {
					// Class cross link found (possiblly to a member in the
					// class)
					result.append(classCrossLink);
				} else {
					// No cross link found so print warning
					// configuration.getDocletSpecificMsg().warning(see.position(),
					// "doclet.see.class_or_package_not_found",tagName,
					// seetext);
					
					result.append((label.length() == 0) ? text : label);
				}
			}
		} else if (refMemName == null) {
			// Must be a class reference since refClass is not null and
			// refMemName is null.
			if (label.length() == 0) {
				label = (isplaintext) ? refClass.name() : getCode()
						+ refClass.name() + getCodeEnd();
				result.append(getLink(new LinkInfoImpl(refClass, label)));
			} else {
				result.append(getLink(new LinkInfoImpl(refClass, label)));
			}
		} else if (refMem == null) {
			// Must be a member reference since refClass is not null and
			// refMemName is not null.
			// However, refMem is null, so this referenced member does not
			// exist.
			result.append((label.length() == 0) ? text : label);
		} else {
			// Must be a member reference since refClass is not null and
			// refMemName is not null.
			// refMem is not null, so this @see tag must be referencing a valid
			// member.
			ClassDoc containing = refMem.containingClass();
			if (see.text().trim().startsWith("#")
					&& !(containing.isPublic() || Util.isLinkable(containing,
							configuration()))) {
				// Since the link is relative and the holder is not even being
				// documented, this must be an inherited link. Redirect it.
				// The current class either overrides the referenced member or
				// inherits it automatically.
				containing = ((ClassWriterImpl) this).getClassDoc();
			}
			if (configuration.currentClass != containing) {
				refMemName = containing.name() + "." + refMemName;
			}
			if (refMem instanceof ExecutableMemberDoc) {
				if (refMemName.indexOf('(') < 0) {
					refMemName += ((ExecutableMemberDoc) refMem).signature();
				}
			}
			text = (isplaintext) ? refMemName : getCode() + refMemName
					+ getCodeEnd();
			
			result.append(getDocLink(LinkInfoImpl.CONTEXT_SEE_TAG, containing,
					refMem, (label.length() == 0) ? text : label, false));
		}
		return result.toString();
	}
	
	public void printInlineComment(Doc doc, Tag tag) {
		printCommentTags(doc, tag.inlineTags(), false, false);
	}
	
	public void printInlineDeprecatedComment(Doc doc, Tag tag) {
		printCommentTags(doc, tag.inlineTags(), true, false);
	}
	
	public void printSummaryComment(Doc doc) {
		printSummaryComment(doc, doc.firstSentenceTags());
	}
	
	public void printSummaryComment(Doc doc, Tag[] firstSentenceTags) {
		printCommentTags(doc, firstSentenceTags, false, true);
	}
	
	public void printSummaryDeprecatedComment(Doc doc) {
		printCommentTags(doc, doc.firstSentenceTags(), true, true);
	}
	
	public void printSummaryDeprecatedComment(Doc doc, Tag tag) {
		printCommentTags(doc, tag.firstSentenceTags(), true, true);
	}
	
	public void printInlineComment(Doc doc) {
		printCommentTags(doc, doc.inlineTags(), false, false);
		p();
	}
	
	public void printInlineDeprecatedComment(Doc doc) {
		printCommentTags(doc, doc.inlineTags(), true, false);
	}
	
	private void printCommentTags(Doc doc, Tag[] tags, boolean depr,
			boolean first) {
		if (configuration.nocomment) {
			return;
		}
		if (depr) {
			italic();
		}
		String result = commentTagsToString(null, doc, tags, first);
		print(result);
		if (depr) {
			italicEnd();
		}
		if (tags.length == 0) {
			space();
		}
	}
	
	/**
	 * Converts inline tags and text to text strings, expanding the inline tags
	 * along the way. Called wherever text can contain an inline tag, such as in
	 * comments or in free-form text arguments to non-inline tags.
	 * 
	 * @param holderTag specific tag where comment resides
	 * @param doc specific doc where comment resides
	 * @param tags array of text tags and inline tags (often alternating)
	 *        present in the text of interest for this doc
	 * @param isFirstSentence true if text is first sentence
	 */
	public String commentTagsToString(Tag holderTag, Doc doc, Tag[] tags,
			boolean isFirstSentence) {
		StringBuffer result = new StringBuffer();
		// Array of all possible inline tags for this javadoc run
		configuration.tagletManager.checkTags(doc, tags, true);
		for (int i = 0; i < tags.length; i++) {
			Tag tagelem = tags[i];
			String tagName = tagelem.name();
			if (tagelem instanceof SeeTag) {
				result.append(seeTagToString((SeeTag) tagelem));
			} else if (!tagName.equals("Text")) {
				TagletOutput output = TagletWriter.getInlineTagOuput(
						configuration.tagletManager, holderTag, (Tag) tagelem,
						getTagletWriterInstance(isFirstSentence));
				result.append(output == null ? "" : output.toString());
				continue;
			} else {
				// This is just a regular text tag. The text may contain html
				// links (<a>)
				// or inline tag {@docRoot}, which will be handled as special
				// cases.
				String text = redirectRelativeLinks(tagelem.holder(),
						tagelem.text());
				
				// Replace @docRoot only if not represented by an instance of
				// DocRootTaglet,
				// that is, only if it was not present in a source file doc
				// comment.
				// This happens when inserted by the doclet (a few lines
				// above in this method). [It might also happen when passed in
				// on the command
				// line as a text argument to an option (like -header).]
				text = replaceDocRootDir(text);
				if (isFirstSentence) {
					text = removeNonInlineHtmlTags(text);
				}
				StringTokenizer lines = new StringTokenizer(text, "\r\n", true);
				StringBuffer textBuff = new StringBuffer();
				while (lines.hasMoreTokens()) {
					StringBuffer line = new StringBuffer(lines.nextToken());
					Util.replaceTabs(configuration.sourcetab, line);
					textBuff.append(line.toString());
				}
				result.append(textBuff);
			}
		}
		return result.toString();
	}
	
	/**
	 * Return true if relative links should not be redirected.
	 * 
	 * @return Return true if a relative link should not be redirected.
	 */
	private boolean shouldNotRedirectRelativeLinks() {
		return this instanceof AnnotationTypeWriter
				|| this instanceof ClassWriter
				|| this instanceof PackageSummaryWriter;
	}
	
	/**
	 * Suppose a piece of documentation has a relative link. When you copy that
	 * documetation to another place such as the index or class-use page, that
	 * relative link will no longer work. We should redirect those links so that
	 * they will work again.
	 * <p>
	 * Here is the algorithm used to fix the link:
	 * <p>
	 * &lt;relative link&gt; => docRoot + &lt;relative path to file&gt; +
	 * &lt;relative link&gt;
	 * <p>
	 * For example, suppose com.sun.javadoc.RootDoc has this link: &lt;a
	 * href="package-summary.html"&gt;The package Page&lt;/a&gt;
	 * <p>
	 * If this link appeared in the index, we would redirect the link like this:
	 * 
	 * &lt;a href="./com/sun/javadoc/package-summary.html"&gt;The package
	 * Page&lt;/a&gt;
	 * 
	 * @param doc the Doc object whose documentation is being written.
	 * @param text the text being written.
	 * 
	 * @return the text, with all the relative links redirected to work.
	 */
	private String redirectRelativeLinks(Doc doc, String text) {
		if (doc == null || shouldNotRedirectRelativeLinks()) {
			return text;
		}
		
		String redirectPathFromRoot;
		if (doc instanceof ClassDoc) {
			redirectPathFromRoot = DirectoryManager.getDirectoryPath(((ClassDoc) doc).containingPackage());
		} else if (doc instanceof MemberDoc) {
			redirectPathFromRoot = DirectoryManager.getDirectoryPath(((MemberDoc) doc).containingPackage());
		} else if (doc instanceof PackageDoc) {
			redirectPathFromRoot = DirectoryManager.getDirectoryPath((PackageDoc) doc);
		} else {
			return text;
		}
		
		if (!redirectPathFromRoot.endsWith(DirectoryManager.URL_FILE_SEPERATOR)) {
			redirectPathFromRoot += DirectoryManager.URL_FILE_SEPERATOR;
		}
		
		// Redirect all relative links.
		int end, begin = text.toLowerCase().indexOf("<a");
		if (begin >= 0) {
			StringBuffer textBuff = new StringBuffer(text);
			
			while (begin >= 0) {
				begin = textBuff.indexOf("=", begin) + 1;
				end = textBuff.indexOf(">", begin + 1);
				if (begin == 0) {
					// Link has no equal symbol.
					configuration.getDocletSpecificMsg().warning(
							doc.position(), "doclet.malformed_html_link_tag",
							text);
					break;
				}
				if (end == -1) {
					// Break without warning. This <a> tag is not necessarily
					// malformed. The text
					// might be missing '>' character because the href has an
					// inline tag.
					break;
				}
				if (textBuff.substring(begin, end).indexOf("\"") != -1) {
					begin = textBuff.indexOf("\"", begin) + 1;
					end = textBuff.indexOf("\"", begin + 1);
					if (begin == 0 || end == -1) {
						// Link is missing a quote.
						break;
					}
				}
				String relativeLink = textBuff.substring(begin, end);
				if (!(relativeLink.toLowerCase().startsWith("mailto:")
						|| relativeLink.toLowerCase().startsWith("http:")
						|| relativeLink.toLowerCase().startsWith("https:") || relativeLink.toLowerCase().startsWith(
						"file:"))) {
					relativeLink = "{@" + (new DocRootTaglet()).getName() + "}"
							+ redirectPathFromRoot + relativeLink;
					textBuff.replace(begin, end, relativeLink);
				}
				begin = textBuff.toString().toLowerCase().indexOf("<a",
						begin + 1);
			}
			return textBuff.toString();
		}
		return text;
	}
	
	public String removeNonInlineHtmlTags(String text) {
		if (text.indexOf('<') < 0) {
			return text;
		}
		String noninlinetags[] = { "<ul>", "</ul>", "<ol>", "</ol>", "<dl>",
				"</dl>", "<table>", "</table>", "<tr>", "</tr>", "<td>",
				"</td>", "<th>", "</th>", "<p>", "</p>", "<li>", "</li>",
				"<dd>", "</dd>", "<dir>", "</dir>", "<dt>", "</dt>", "<h1>",
				"</h1>", "<h2>", "</h2>", "<h3>", "</h3>", "<h4>", "</h4>",
				"<h5>", "</h5>", "<h6>", "</h6>", "<pre>", "</pre>", "<menu>",
				"</menu>", "<listing>", "</listing>", "<hr>", "<blockquote>",
				"</blockquote>", "<center>", "</center>", "<UL>", "</UL>",
				"<OL>", "</OL>", "<DL>", "</DL>", "<TABLE>", "</TABLE>",
				"<TR>", "</TR>", "<TD>", "</TD>", "<TH>", "</TH>", "<P>",
				"</P>", "<LI>", "</LI>", "<DD>", "</DD>", "<DIR>", "</DIR>",
				"<DT>", "</DT>", "<H1>", "</H1>", "<H2>", "</H2>", "<H3>",
				"</H3>", "<H4>", "</H4>", "<H5>", "</H5>", "<H6>", "</H6>",
				"<PRE>", "</PRE>", "<MENU>", "</MENU>", "<LISTING>",
				"</LISTING>", "<HR>", "<BLOCKQUOTE>", "</BLOCKQUOTE>",
				"<CENTER>", "</CENTER>" };
		for (int i = 0; i < noninlinetags.length; i++) {
			text = replace(text, noninlinetags[i], "");
		}
		return text;
	}
	
	public String replace(String text, String tobe, String by) {
		while (true) {
			int startindex = text.indexOf(tobe);
			if (startindex < 0) {
				return text;
			}
			int endindex = startindex + tobe.length();
			StringBuffer replaced = new StringBuffer();
			if (startindex > 0) {
				replaced.append(text.substring(0, startindex));
			}
			replaced.append(by);
			if (text.length() > endindex) {
				replaced.append(text.substring(endindex));
			}
			text = replaced.toString();
		}
	}
	
	public void printStyleSheetProperties() {
		String filename = configuration.stylesheetfile;
		if (filename.length() > 0) {
			File stylefile = new File(filename);
			String parent = stylefile.getParent();
			filename = (parent == null)
					? filename
					: filename.substring(parent.length() + 1);
		} else {
			filename = "stylesheet.css";
		}
		filename = relativePath + filename;
		link("REL =\"stylesheet\" TYPE=\"text/css\" HREF=\"" + filename + "\" "
				+ "TITLE=\"Style\"");
	}
	
	/**
	 * According to the Java Language Specifications, all the outer classes and
	 * static nested classes are core classes.
	 */
	public boolean isCoreClass(ClassDoc cd) {
		return cd.containingClass() == null || cd.isStatic();
	}
	
	/**
	 * Write the annotatation types for the given packageDoc.
	 * 
	 * @param packageDoc the package to write annotations for.
	 */
	public void writeAnnotationInfo(PackageDoc packageDoc) {
		writeAnnotationInfo(packageDoc, packageDoc.annotations());
	}
	
	/**
	 * Write the annotatation types for the given doc.
	 * 
	 * @param doc the doc to write annotations for.
	 */
	public void writeAnnotationInfo(ProgramElementDoc doc) {
		writeAnnotationInfo(doc, doc.annotations());
	}
	
	/**
	 * Write the annotatation types for the given doc and parameter.
	 * 
	 * @param indent the number of spaced to indent the parameters.
	 * @param doc the doc to write annotations for.
	 * @param param the parameter to write annotations for.
	 */
	public boolean writeAnnotationInfo(int indent, Doc doc, Parameter param) {
		return writeAnnotationInfo(indent, doc, param.annotations(), false);
	}
	
	/**
	 * Write the annotatation types for the given doc.
	 * 
	 * @param doc the doc to write annotations for.
	 * @param descList the array of {@link AnnotationDesc}.
	 */
	private void writeAnnotationInfo(Doc doc, AnnotationDesc[] descList) {
		writeAnnotationInfo(0, doc, descList, true);
	}
	
	/**
	 * Write the annotatation types for the given doc.
	 * 
	 * @param indent the number of extra spaces to indent the annotations.
	 * @param doc the doc to write annotations for.
	 * @param descList the array of {@link AnnotationDesc}.
	 */
	private boolean writeAnnotationInfo(int indent, Doc doc,
			AnnotationDesc[] descList, boolean lineBreak) {
		List annotations = getAnnotations(indent, descList, lineBreak);
		if (annotations.size() == 0) {
			return false;
		}
		fontNoNewLine("-1");
		for (Iterator iter = annotations.iterator(); iter.hasNext();) {
			print((String) iter.next());
		}
		fontEnd();
		return true;
	}
	
	/**
	 * Return the string representations of the annotation types for the given
	 * doc.
	 * 
	 * @param indent the number of extra spaces to indent the annotations.
	 * @param descList the array of {@link AnnotationDesc}.
	 * @param linkBreak if true, add new line between each member value.
	 * @return an array of strings representing the annotations being
	 *         documented.
	 */
	private List getAnnotations(int indent, AnnotationDesc[] descList,
			boolean linkBreak) {
		List results = new ArrayList();
		StringBuffer annotation;
		for (int i = 0; i < descList.length; i++) {
			AnnotationTypeDoc annotationDoc = descList[i].annotationType();
			if (!Util.isDocumentedAnnotation(annotationDoc)) {
				continue;
			}
			annotation = new StringBuffer();
			LinkInfoImpl linkInfo = new LinkInfoImpl(
					LinkInfoImpl.CONTEXT_ANNOTATION, annotationDoc);
			linkInfo.label = "@" + annotationDoc.name();
			annotation.append(getLink(linkInfo));
			AnnotationDesc.ElementValuePair[] pairs = descList[i].elementValues();
			if (pairs.length > 0) {
				annotation.append('(');
				for (int j = 0; j < pairs.length; j++) {
					if (j > 0) {
						annotation.append(",");
						if (linkBreak) {
							annotation.append(DocletConstants.NL);
							int spaces = annotationDoc.name().length() + 2;
							for (int k = 0; k < (spaces + indent); k++) {
								annotation.append(' ');
							}
						}
					}
					annotation.append(getDocLink(
							LinkInfoImpl.CONTEXT_ANNOTATION,
							pairs[j].element(), pairs[j].element().name(),
							false));
					annotation.append('=');
					AnnotationValue annotationValue = pairs[j].value();
					List annotationTypeValues = new ArrayList();
					if (annotationValue.value() instanceof AnnotationValue[]) {
						AnnotationValue[] annotationArray = (AnnotationValue[]) annotationValue.value();
						for (int k = 0; k < annotationArray.length; k++) {
							annotationTypeValues.add(annotationArray[k]);
						}
					} else {
						annotationTypeValues.add(annotationValue);
					}
					annotation.append(annotationTypeValues.size() == 1
							? ""
							: "{");
					for (Iterator iter = annotationTypeValues.iterator(); iter.hasNext();) {
						annotation.append(annotationValueToString((AnnotationValue) iter.next()));
						annotation.append(iter.hasNext() ? "," : "");
					}
					annotation.append(annotationTypeValues.size() == 1
							? ""
							: "}");
				}
				annotation.append(")");
			}
			annotation.append(linkBreak ? DocletConstants.NL : "");
			results.add(annotation.toString());
		}
		return results;
	}
	
	private String annotationValueToString(AnnotationValue annotationValue) {
		if (annotationValue.value() instanceof Type) {
			Type type = (Type) annotationValue.value();
			if (type.asClassDoc() != null) {
				LinkInfoImpl linkInfo = new LinkInfoImpl(
						LinkInfoImpl.CONTEXT_ANNOTATION, type);
				linkInfo.label = (type.asClassDoc().isIncluded()
						? type.typeName()
						: type.qualifiedTypeName())
						+ type.dimension() + ".class";
				return getLink(linkInfo);
			} else {
				return type.typeName() + type.dimension() + ".class";
			}
		} else if (annotationValue.value() instanceof AnnotationDesc) {
			List list = getAnnotations(
					0,
					new AnnotationDesc[] { (AnnotationDesc) annotationValue.value() },
					false);
			StringBuffer buf = new StringBuffer();
			for (Iterator iter = list.iterator(); iter.hasNext();) {
				buf.append(iter.next());
			}
			return buf.toString();
		} else if (annotationValue.value() instanceof MemberDoc) {
			return getDocLink(LinkInfoImpl.CONTEXT_ANNOTATION,
					(MemberDoc) annotationValue.value(),
					((MemberDoc) annotationValue.value()).name(), false);
		} else {
			return annotationValue.toString();
		}
	}
	
	/**
	 * Return the configuation for this doclet.
	 * 
	 * @return the configuration for this doclet.
	 */
	public Configuration configuration() {
		return configuration;
	}
}
